package br.com.puertorico.model.service.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.envers.query.criteria.AuditCriterion;
import org.hibernate.envers.query.criteria.IdentifierEqAuditExpression;
import org.hibernate.envers.query.order.AuditOrder;
import org.hibernate.envers.query.order.PropertyAuditOrder;
import org.hibernate.envers.query.property.PropertyNameGetter;
import org.hibernate.envers.query.property.RevisionNumberPropertyName;

import br.com.puertorico.common.exceptions.SystemException;
import br.com.puertorico.model.entity.BaseEntity;
import br.com.puertorico.model.entity.audit.BaseRevision;
import br.com.puertorico.model.entity.audit.EntityVersion;
import br.com.puertorico.model.querybuilder.QueryBuilder;
import br.com.puertorico.model.querybuilder.sorting.SingleSortStrategy;
import br.com.puertorico.model.querybuilder.sorting.SortItem;
import br.com.puertorico.model.querybuilder.sorting.SortStrategy;
import br.com.puertorico.model.service.exceptions.DatabaseException;

@SuppressWarnings("unchecked")
public class PersistenceUtils {

	private static final Validator validator;

	static {
		validator = Validation.buildDefaultValidatorFactory().getValidator();
	}

	public static <DTO, CRITERIA> List<DTO> search(EntityManager em,
			CRITERIA criteria, int pageSize, int first, String sortField,
			boolean asc) {
		SortStrategy sortStrategy = new SingleSortStrategy();
		if (sortField != null && !sortField.isEmpty()) {
			SortItem item = new SortItem(sortField, asc, 0);
			sortStrategy.getSortItemMap().put(sortField, item);
		}
		Query query = QueryBuilder.buildQuery(em, criteria, pageSize, first,
				sortStrategy);
		return query.getResultList();
	}

	public static <CRITERIA> Long getRowCount(EntityManager em,
			CRITERIA criteria) {
		Query query = QueryBuilder.buildQueryCount(em, criteria);
		return (Long) query.getSingleResult();
	}

	public static <ENTITY extends BaseEntity> ENTITY save(EntityManager em,
			ENTITY entity) throws DatabaseException {
		boolean isNewObject = (entity.getId() == null);
		try {
			// validar
			Set<ConstraintViolation<ENTITY>> constraintViolations = validator
					.validate(entity);
			if (!constraintViolations.isEmpty()) {
				DatabaseException databaseException = new DatabaseException(
						constraintViolations);
				throw databaseException;
			}
			entity = em.merge(entity);
			flush(em, entity);
			return entity;
		} catch (DatabaseException e) {
			e.setParams(entity.getId().toString());
			if (isNewObject) {
				cleanPk(entity);
			}
			throw e;
		} catch (Exception e) {
			throw new SystemException(e);
		}
	}

	public static <ENTITY extends BaseEntity> void remove(EntityManager em,
			ENTITY entity) throws DatabaseException {
		try {
			entity = (ENTITY) em.find(entity.getClass(), entity.getId());
			em.remove(entity);
			flush(em, entity);
		} catch (DatabaseException e) {
			e.setParams(entity.getId().toString());
			throw e;
		} catch (Exception e) {
			throw new SystemException(e);
		}
	}

	/**
	 * Busca todas as vers&otilde;es de uma entidade
	 *
	 * @param <ENTITY>
	 * @param entity
	 * @return
	 */
	public static <ENTITY extends BaseEntity, REVISION extends BaseRevision> List<EntityVersion<ENTITY, REVISION>> entityHistory(
			EntityManager em, ENTITY entity, Class<REVISION> revisionClass) {

		AuditReader auditReader = AuditReaderFactory.get(em);
		List<EntityVersion<ENTITY, REVISION>> retorno = new ArrayList<EntityVersion<ENTITY, REVISION>>();
		for (Number revisionNumber : auditReader.getRevisions(
				entity.getClass(), entity.getId())) {
			em.find(revisionClass, revisionNumber.longValue());
		}
		// procurar o tipo da revisao
		AuditQuery query = auditReader.createQuery().forRevisionsOfEntity(
				entity.getClass(), false, true);
		AuditCriterion c = new IdentifierEqAuditExpression(entity.getId(), true);
		query.add(c);
		PropertyNameGetter p = new RevisionNumberPropertyName();
		AuditOrder order = new PropertyAuditOrder(p, false);
		query.addOrder(order);

		for (Object obj : query.getResultList()) {
			Object[] array = (Object[]) obj;
			ENTITY entidade = (ENTITY) array[0];
			REVISION revisao = (REVISION) array[1];
			RevisionType tipoRevisao = (RevisionType) array[2];
			revisao.setTipoRevisao(tipoRevisao);
			retorno.add(new EntityVersion<ENTITY, REVISION>(entidade, revisao));
		}
		return retorno;
	}

	private static void flush(EntityManager em, BaseEntity entity)
			throws DatabaseException {
		try {
			em.flush();
		} catch (Exception e) {
			throw new DatabaseException(e, entity.getId(), entity.getClass());
		}
	}

	private static void cleanPk(BaseEntity entity) {
		if (entity == null) {
			return;
		}
		entity.setId(null);
	}

}